package com.atguigu.tingshu.album.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.atguigu.tingshu.album.mapper.AlbumAttributeValueMapper;
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.album.service.VodService;
import com.atguigu.tingshu.common.cache.GuiguCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.query.album.TrackInfoQuery;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

    @Autowired
    private AlbumInfoMapper albumInfoMapper;

    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;

    @Autowired
    private AlbumStatMapper albumStatMapper;

    @Autowired
    private TrackInfoMapper trackInfoMapper;

    @Autowired
    private VodService vodService;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private UserFeignClient userFeignClient;

    /**
     * 新增专辑
     * 1.将提交的信息封装到AlbumInfo对象中，保存在专辑信息表
     * 2.将标签值AlbumAttributeValue列表，保存在专辑标签关系表中。
     * 3.保存专辑统计记录。
     *
     * @param albumInfoVo 专辑vo对象
     * @param userId      用户id
     */
    @Override
    //在开启事务后，默认情况下发生RuntimeException或者Error时才会回滚。
    @Transactional(rollbackFor = Exception.class)
    public void saveAlbumInfo(AlbumInfoVo albumInfoVo, Long userId) {
        //对用户填写的部分内容进行文本审核
        String content = albumInfoVo.getAlbumTitle() + albumInfoVo.getAlbumIntro();
        String suggest = vodService.scanContent(content);
        if ("block".equals(suggest) || "review".equals(suggest)) {
            throw new RuntimeException("内容含有敏感信息，请修改！");
        }

        //1. 将提交的信息封装到AlbumInfo对象中，保存在专辑信息表
        //1.1 将专辑vo对象转换为po对象
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);

        //1.2 为专辑对象中的其他属性赋值
        albumInfo.setUserId(userId);
        //设置前5集是免费的
        albumInfo.setTracksForFree(5);

        //1.2.1 审核状态设置为：未通过 TODO：后续采用内容审核接口来进行检测。
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
        //最后将专辑信息插入到数据库中
        albumInfoMapper.insert(albumInfo);

        //2. 将标签值AlbumAttributeValue列表，保存在专辑标签关系表中。
        //获取专辑id
        Long albumId = albumInfo.getId();
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
            //2.1 封装AlbumAttributeValue对象
            AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
            albumAttributeValue.setAttributeId(albumAttributeValueVo.getAttributeId());
            albumAttributeValue.setValueId(albumAttributeValueVo.getValueId());

            //2.2 关联专辑id
            albumAttributeValue.setAlbumId(albumId);
            //将信息插入到数据库中
            albumAttributeValueMapper.insert(albumAttributeValue);
        }


        //3. 保存专辑统计记录,为新增的专辑设置四条初始统计记录。
        saveAlbumStatus(albumId, SystemConstant.ALBUM_STAT_PLAY, 0);
        saveAlbumStatus(albumId, SystemConstant.ALBUM_STAT_SUBSCRIBE, 0);
        saveAlbumStatus(albumId, SystemConstant.ALBUM_STAT_BUY, 0);
        saveAlbumStatus(albumId, SystemConstant.ALBUM_STAT_COMMENT, 0);

        //4.发送上架消息到mq，完成自动上架。
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_UPPER, albumId);

    }

    /**
     * 保存专辑统计信息
     *
     * @param albumId  专辑id
     * @param statType 统计类型 0401-播放量 0402-订阅量 0403-购买量 0403-评论数
     * @param statNum  统计数目
     */
    @Override
    public void saveAlbumStatus(Long albumId, String statType, Integer statNum) {
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatType(statType);
        albumStat.setStatNum(statNum);

        albumStatMapper.insert(albumStat);

    }

    /**
     * @param pageInfo
     * @param query
     * @return
     */
    @Override
    public Page<AlbumListVo> findUserAlbumPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery query) {
        //调用持久层进行分页查询
        return albumInfoMapper.findUserAlbumPage(pageInfo, query);

    }

    /**
     * 根据id删除专辑
     *
     * @param id
     */
    /*
    @Transactional注解失效的场景：
    1.catch处理异常没有抛出，例如e.printStackTrace()
    2.在同一个类中，没有事务管理的方法内部调用了有事务管理的方法，
    由于调用的当前方法不是代理对象方法，所以会造成事务失效
    3.方法的修饰符是private或者protected，无法产生代理对象，所以会造成事务失效
    4.方法被fianl/static修饰也会导致事务失效，因为cglib无法代理final方法，jdk动态代理无法代理static方法。
    5.采用多线程也会导致事务失效，因为：每个线程操作数据库都有各自的SqlSession对象。
    6.数据库采用MyISAM存储引擎，不支持事务。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeAlbumInfo(Long id) {
        //1.根据专辑id查询该专辑下是否关联声音，如果存在则不允许删除
        LambdaQueryWrapper<TrackInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TrackInfo::getAlbumId, id)
                /*last()方法：
                作用是在生成的sql语句最后拼接自定义的sql片段。
                例如：select * from table where id = ? limit 1
                只返回第一个符合条件的结果。
                */
                .last("limit 1");
        Long count = trackInfoMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new GuiguException(500, "该专辑下还有声音，请先删除该专辑下的声音。");
        }

        //2.删除专辑记录
        //这两个语句的功能基本一样。
//        albumInfoMapper.delete(new LambdaQueryWrapper<AlbumInfo>()
//                .eq(AlbumInfo::getId, id));
        albumInfoMapper.deleteById(id);


        //3.根据专辑id删除统计记录
        albumStatMapper.delete(new LambdaQueryWrapper<AlbumStat>()
                .eq(AlbumStat::getAlbumId, id));


        //4.根据专辑id删除专辑标签记录
        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>()
                .eq(AlbumAttributeValue::getAlbumId, id));

        //5.发送下架消息到mq，完成自动下架。
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_LOWER, id);
    }

    /**
     * 封装从数据库中获取数据的方法
     * 根据专辑id查询专辑信息，包含标签列表
     *
     * @param id
     * @return
     */
    @Override
    @GuiguCache(prefix = RedisConstant.ALBUM_INFO_PREFIX)
    public AlbumInfo getAlbumInfoFromDB(Long id) {
        //1.根据专辑id查询专辑基本信息
        AlbumInfo albumInfo = albumInfoMapper.selectById(id);

        //2.根据专辑id查询专辑标签列表
        LambdaQueryWrapper<AlbumAttributeValue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AlbumAttributeValue::getAlbumId, id);
        List<AlbumAttributeValue> albumAttributeValueList = albumAttributeValueMapper.selectList(queryWrapper);
        if (CollectionUtil.isNotEmpty(albumAttributeValueList)) {
            albumInfo.setAlbumAttributeValueVoList(albumAttributeValueList);
        }

        return albumInfo;

    }

    /**
     * 修改专辑信息
     *
     * @param albumInfo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAlbumInfo(AlbumInfo albumInfo) {
        //对用户填写的部分内容进行文本审核
        String content = albumInfo.getAlbumTitle() + albumInfo.getAlbumIntro();
        String suggest = vodService.scanContent(content);
        if ("block".equals(suggest) || "review".equals(suggest)) {
            throw new RuntimeException("内容含有敏感信息，请修改！");
        }

        //1.修改专辑信息
        //初始值先设置审核不通过，待审核通过后，再修改为审核通过
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_NO_PASS);
        albumInfoMapper.updateById(albumInfo);

        //2.删除原有的专辑标签记录
        LambdaQueryWrapper<AlbumAttributeValue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AlbumAttributeValue::getAlbumId, albumInfo.getId());
        albumAttributeValueMapper.delete(queryWrapper);

        //3.新增提交的专辑标签关系
        List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            for (AlbumAttributeValue albumAttributeValue : albumAttributeValueVoList) {
                //将专辑id添加到album_attribute_value表中。
                albumAttributeValue.setAlbumId(albumInfo.getId());
                albumAttributeValueMapper.insert(albumAttributeValue);
            }
        }

        //4.发送专辑信息到mq，完成自动上架。
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_UPPER, albumInfo.getId());

        //TODO 先更新database数据库
        //TODO 再更新缓存

    }

    /**
     * 获取当前用户全部专辑列表
     *
     * @param userId
     * @return
     */
    @Override
    public List<AlbumInfo> findUserAllAlbumList(Long userId) {
        //本业务的sql实现
        /*
        select id, album_title
        from album_info
        where user_id = 1
            and is_deleted = 0;
         */
        LambdaQueryWrapper<AlbumInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AlbumInfo::getUserId, userId)
                .select(AlbumInfo::getId, AlbumInfo::getAlbumTitle);

        //限制返回记录数
        queryWrapper.last("limit 200");
        //设置降序排序
        queryWrapper.orderByDesc(AlbumInfo::getId);

        List<AlbumInfo> list = albumInfoMapper.selectList(queryWrapper);

        return list;

    }

    /**
     * 查询当前用户声音列表
     *
     * @param pageInfo       分页对象
     * @param trackInfoQuery 查询条件
     * @return
     */
    @Override
    public Page<TrackListVo> findUserTrackPage(Page<TrackListVo> pageInfo, TrackInfoQuery trackInfoQuery) {
        return trackInfoMapper.findUserTrackPage(pageInfo, trackInfoQuery);
    }

    /**
     * 根据专辑ID获取专辑统计信息
     *
     * @param albumId
     * @return
     */
    @Override
    @GuiguCache(prefix = RedisConstant.ALBUM_INFO_PREFIX + "stat")
    public AlbumStatVo getAlbumStatVo(Long albumId) {
        return albumInfoMapper.getAlbumStatVo(albumId);
    }

    /**
     * 获取专辑信息
     * 利用Redis缓存进行缓存优化；
     * 采用Redisson分布式所解决缓存击穿问题，避免大量请求同时查询数据库。
     *
     * @param id
     * @return
     */
    @Override
    public AlbumInfo getAlbumInfo(Long id) {
        try {
            //1.优先从分布式缓存Redis中获取业务数据，命中缓存以后直接返回即可。
            //1.1 构建缓存业务数据key
            String dataKey = RedisConstant.ALBUM_INFO_PREFIX + id;
            //1.2 尝试从Redis中获取业务数据
            AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
            //1.3 命中缓存返回即可
            //如果不为空，直接返回即可。如果为空，继续执行，不要抛异常
            if (albumInfo != null) {
                return albumInfo;
            }

            //2.未命中缓存，获取分布式锁
            //2.1 构建分布式锁key，形式：业务key：锁后缀
            String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
            //2.2 创建锁对象
            RLock lock = redissonClient.getLock(lockKey);
            //2.3 尝试获取锁，获取锁成功，执行业务逻辑。
            boolean flag = lock.tryLock();

            //3.获取分布式锁成功以后执行业务逻辑：将数据写入缓存，响应业务数据。
            if (flag) {
                try {
                    //3.1 从数据库中查询数据 执行封装查询数据库的方法
                    albumInfo = this.getAlbumInfoFromDB(id);
                    //3.2 过期时间设置为固定时间+随机时间
                    long ttl = RedisConstant.ALBUM_TIMEOUT + RandomUtil.randomInt(0, 1000);
                    redisTemplate.opsForValue().set(dataKey, albumInfo, ttl, TimeUnit.SECONDS);
                    return albumInfo;
                } finally {
                    //4.释放锁对象
                    lock.unlock();
                }

            } else {
                //5.获取锁对象失败，自旋
                return this.getAlbumInfo(id);
            }
        } catch (Exception e) {
            log.error("[专辑服务]获取专辑服务异常:{}", e);
            //兜底的处理方案：当Redis突然宕机的时候，系统直接从数据库中查询数据。
            return this.getAlbumInfoFromDB(id);
        }


    }

    /**
     * 查询当前用户购买专辑声音-展示分集购买列表
     *
     * @param trackId 选择购买的声音id 以此声音id作为起始条件
     * @return [{price:BigDecimal,name:String,trackCount:int},....]
     */
    @Override
    public List<Map<String, Object>> findUserTrackPaidList(Long userId, Long trackId) {
        //1.根据预购声音id查询选择声音信息，得到专辑id以及声音序号
        TrackInfo trackInfo = trackInfoMapper.selectById(trackId);
        Long albumId = trackInfo.getAlbumId();
        Integer orderNum = trackInfo.getOrderNum();

        //2.根据条件：专辑id和声音序号 查询"待购买"的声音列表
        LambdaQueryWrapper<TrackInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TrackInfo::getAlbumId, albumId)
                .ge(TrackInfo::getOrderNum, orderNum)
                .select(TrackInfo::getId);
        List<TrackInfo> waitBuyTrackInfoList = trackInfoMapper.selectList(queryWrapper);
        Assert.notNull(waitBuyTrackInfoList, "待购买声音列表为空");

        //3.远程调用用户服务，获取该专辑下用户已经购买的声音列表
        List<Long> userPaidTrackIdList = userFeignClient.findUserPaidTrackIdList(userId, albumId).getData();

        //4.判断是否存在已购买的声音，在待购买声音列表中移除已购买的声音。
        if (CollectionUtil.isNotEmpty(userPaidTrackIdList)) {
            waitBuyTrackInfoList = waitBuyTrackInfoList.stream()
                    .filter(trackInfo1 -> {
                        //把买过的排除在外
                        return !userPaidTrackIdList.contains(trackInfo1.getId());
                    }).collect(Collectors.toList());

        }


        //5.根据最终真正的未购买的声音id长度，动态构建分集购买的对象集合。
        List<Map<String, Object>> mapList = new ArrayList<>();
        int size = waitBuyTrackInfoList.size();

        //5.0 查询专辑表格获取声音单价 声音不支持折扣
        AlbumInfo albumInfo = this.getAlbumInfo(albumId);
        BigDecimal price = albumInfo.getPrice();

        //5.1 构建"本集"分集购买对象
        Map<String, Object> currentMap = new HashMap<>();
        currentMap.put("price", price);
        currentMap.put("name", "本集");
        currentMap.put("trackCount", 1);
        mapList.add(currentMap);

        //5.2 构建其他分集购买对象，例如：后10集，全集等等。
        for (int i = 10; i <= 50; i += 10) {
            //如果待购买的分集数量大于当前的i值，则显示
            if (size > i) {
                Map<String, Object> map = new HashMap<>();
                map.put("price", price.multiply(BigDecimal.valueOf(i)));
                map.put("name", "后" + i + "集");
                map.put("trackCount", i);
                mapList.add(map);
            } else {
                //否则的话直接展示全集
                Map<String, Object> map = new HashMap<>();
                map.put("price", price.multiply(BigDecimal.valueOf(size)));
                map.put("name", "全集(后"+size+"集)");
                map.put("trackCount", size);
                mapList.add(map);
                break;
            }

        }

        return mapList;
    }

}
